package com.erp.base.common.utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Loger
 * Date: 2018-07-30
 * TIme: 23:54
 * Description :
 */
public class JedisUtils {

    private static Logger logger=LoggerFactory.getLogger(JedisUtils.class);
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final Long RELEASE_SUCCESS = 1L;

    //private static boolean testOnBorrow;
   
    /**
     * 获取jedis
     *
     * @return
     */
    public static Jedis getJedis() {
        return SpringContextHolder.getBean(Jedis.class);
    }

    /**
     * jedis放回连接池
     *
     * @param jedis
     */
    public static void close(Jedis jedis) {
        //从源码可以分析得到，如果是使用连接池的形式，这个并非真正的close,而是把连接放回连接池中
        if (jedis != null) {
            jedis.close();
        }
    }
    /**
     * 设置过期时间
     * @param key
     * @param seconds
     */
    public static void expire(String key, int seconds) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.expire(key, seconds);
        } catch (Exception e) {
        	logger.error("set失败,key:"+key+";seconds:"+seconds, e);
        } finally {
            //close(jedis);
        }
    }

    /**
     * get
     *对String操作：返回数据库中名称为key的string的value
     * @param key
     * @return
     */
    public static String get(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.get(key);
        } catch (Exception e) {
        	logger.error("get失败,key:"+key, e);
        	return null;
        } finally {
           // close(jedis);
        }
    }

    /**
     * set
     *对String操作：给数据库中名称为key的string赋予值value
     * @param key
     * @param value
     * @return
     */
    public static void set(String key, String value) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.set(key, value);
        } catch (Exception e) {
        	logger.error("set失败,key:"+key+";value:"+value, e);
        } finally {
           // close(jedis);
        }
    }

    /**
     * set with expire milliseconds
     * 对String操作：给数据库中名称为key的string赋予值value
     * @param key
     * @param value
     * @param seconds
     * @return
     */
    public static void set(String key, String value, int seconds) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            //jedis.set(key, value, "NX", "EX", seconds);
            jedis.set(key, value);
            jedis.expire(key, seconds);
        } catch (Exception e) {
        	logger.error("set失败,key:"+key+";value:"+value+";seconds:"+seconds, e);
        } finally {
           // close(jedis);
        }
    }
    /**
     * 删除redis缓存
     * @param key
     */
    public static void del(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            if(jedis.exists(key)){
            	jedis.del(key);
            }
        } catch (Exception e) {
        	logger.error("del失败,key:"+key, e);
        } finally {
           // close(jedis);
        }
    }
    /**
     * 对String操作：名称为key的string增1操作
     * @param key
     * @return
     */
    public static Long incr(String key){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.incr(key);
        }catch (Exception e){
        	logger.error("incr失败,key:"+key, e);
        	return -100l;
        }finally {
           // close(jedis);
        }
    }
    /**
     * 对hash操作： 向名称为key的hash中添加元素field<—>value
     * @param key
     * @param field
     * @param value
     */
    public static void hset(String key,String field,String value){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.hset(key,field,value);
        }catch (Exception e){
        	logger.error("hset失败,key:"+key+";field:"+field+";value:"+value, e);
        }finally {
           // close(jedis);
        }
    }
    /**
     * 对hash操作：返回名称为key的hash中field对应的value
     * @param key
     * @param field
     * @return
     */
    public static String hget(String key,String field){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hget(key,field);
        }catch (Exception e){
        	logger.error("hget失败,key:"+key+";field:"+field, e);
        	return null;
        }finally {
            //close(jedis);
        }
    }
    /**
     * 对hash操作：返回名称为key的hash中field对应的value
     * @param key
     * @param field
     * @return
     */
    public static Long hincrby(String key,String field,Integer increment){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hincrBy(key,field,increment);
        }catch (Exception e){
        	logger.error("hincrby失败,key:"+key+";field:"+field+";increment:"+increment, e);
        	return null;
        }finally {
            //close(jedis);
        }
    }
    /**
     * 对hash操作：返回名称为key的hash中所有的键（field）及其对应的value
     * @param key
     * @return
     */
    public static Map<String,String> hgetAll(String key){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.hgetAll(key);
        }catch (Exception e){
        	logger.error("hgetAll失败,key:"+key, e);
        	return null;
        }finally {
            //close(jedis);
        }
    }

    /**
     *对list操作：lpop 命令的block版本。即当timeout为0时，若遇到名称为key i的list不存在或该list为空，则命令结束。
     *如果 timeout>0，则遇到上述情况时，等待timeout秒，如果问题没有解决，则对key i+1开始的list执行pop操作。
     * @param timeout 0表示永久 单位秒
     * @param key key
     * @return [key,value]
     */
    public static String blpop(int timeout,String key){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            List<String> list = jedis.blpop(timeout, key);
            return list.get(1);
        }catch (Exception e){
        	logger.error("blpop失败,key:"+key+";timeout"+timeout, e);
        	return null;
        }finally {
            //close(jedis);
        }
    }
    /**
     *对list操作：lpop 命令的block版本。即当timeout为0时，若遇到名称为key i的list不存在或该list为空，则命令结束。
     *如果 timeout>0，则遇到上述情况时，等待timeout秒，如果问题没有解决，则对key i+1开始的list执行pop操作。
     * @param timeout 0表示永久 单位秒
     * @param key key
     * @return [key,value]
     */
    public static String blpop(String key){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            List<String> list = jedis.blpop(0, key);
            return list.get(1);
        }catch (Exception e){
        	logger.error("blpop失败,key:"+key, e);
        	return null;
        }finally {
            //close(jedis);
        }
    }
    /**
     * 对list操作：在名称为key的list头添加一个值为value的 元素
     * @param key
     * @param value
     */
    public static void lpush(String key,String... value){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.lpush(key,value);
        }catch (Exception e){
        	logger.error("lpush失败,key:"+key+";value"+value, e);
        }finally {
           // close(jedis);
        }
    }

    /**
     *对list操作：rpop的block版本
     * @param timeout 0表示永久 单位秒
     * @param key key
     * @return [key,value]
     */
    public static String brpop(int timeout,String key){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            List<String> list = jedis.brpop(timeout, key);
            return list.get(1);
        }catch (Exception e){
        	logger.error("brpop失败,key:"+key+";timeout"+timeout, e);
        	return null;
        }finally {
            //close(jedis);
        }
    }
    /**
     *对list操作：rpop的block版本
     * @param timeout 0表示永久 单位秒
     * @param key key
     * @return [key,value]
     */
    public static String brpop(String key){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            List<String> list = jedis.brpop(0, key);
            return list.get(1);
        }catch (Exception e){
        	logger.error("brpop失败,key:"+key, e);
        	return null;
        }finally {
            //close(jedis);
        }
    }
    /**
     * 对list操作：在名称为key的list尾添加一个值为value的元素
     * @param key
     * @param value
     */
    public static void rpush(String key,String... value){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.rpush(key,value);
        }catch (Exception e){
        	 logger.error("rpush失败,key:"+key+";value"+value, e);
        }finally {
            //close(jedis);
        }
    }

    /**
     * 获取key过期时间 -1表示永久 -2表示该key不存在 0表示失败
     * @param key
     * @return
     */
    public static long ttl(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.ttl(key);
        } catch (Exception e) {
        	logger.error("获取key过期时间失败,key:"+key, e);
        	return 0;
        } finally {
            //close(jedis);
        }
    }


    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
    	 Jedis jedis = null;
         try {
             jedis = getJedis();
             String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
             if (LOCK_SUCCESS.equals(result)) {
                 return true;
             }
             return false;
         } catch (Exception e) {
             logger.error("获取分布式锁失败", e);
        	 return false;
         } finally {
            // close(jedis);
         }
    	
    }
    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(String lockKey, String requestId) {
    	 Jedis jedis = null;
         try {
             jedis = getJedis();
             String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
             Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
             if (RELEASE_SUCCESS.equals(result)) {
                 return true;
             }
             return false;
         } catch (Exception e) {
             logger.error("释放分布式锁失败", e);
             return false;
         } finally {
             //close(jedis);
         }
    }
    
    /**
     * 加锁
     * @param locaName  锁的key
     * @param acquireTimeout  获取超时时间
     * @param timeout   锁的超时时间
     * @return 锁标识
     */
    public static String lockWithTimeout(String locaName,long acquireTimeout, long timeout) {
        String retIdentifier = null;
        Jedis jedis=null;
        try {
            // 获取连接
        	jedis = getJedis();
            // 随机生成一个value
            String lockKey = "lock:" + locaName;
            // 超时时间，上锁后超过此时间则自动释放锁
            int lockExpire = (int)(timeout / 1000);
            String identifier = UUID.randomUUID().toString().replace("-", "");
            // 获取锁的超时时间，超过这个时间则放弃获取锁
            long end = System.currentTimeMillis() + acquireTimeout;
            while (System.currentTimeMillis() < end) {
                if (jedis.setnx(lockKey, identifier) == 1) {
                	jedis.expire(lockKey, lockExpire);
                    // 返回value值，用于释放锁时间确认
                    retIdentifier = identifier;
                    //logger.info("加锁：retIdentifier"+":"+retIdentifier);
                    System.out.println(Thread.currentThread().getName()+"加锁：retIdentifier"+":"+retIdentifier);
                    return retIdentifier;
                }
                // 返回-1代表key没有设置超时时间，为key设置一个超时时间
                if (jedis.ttl(lockKey) == -1) {
                	jedis.expire(lockKey, lockExpire);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	 // close(jedis);
        }
        return retIdentifier;
    }

    /**
     * 释放锁
     * @param lockName 锁的key
     * @param identifier    释放锁的标识
     * @return
     */
    public static boolean releaseLock(String lockName, String identifier) {
        Jedis jedis = null;
        String lockKey = "lock:" + lockName;
        boolean retFlag = false;
        try {
        	jedis = getJedis();
            while (true) {
                // 监视lock，准备开始事务
            	jedis.watch(lockKey);
                // 通过前面返回的value值判断是不是该锁，若是该锁，则删除，释放锁
                if (identifier.equals(jedis.get(lockKey))) {
                    Transaction transaction = jedis.multi();
                    transaction.del(lockKey);
                    System.out.println(Thread.currentThread().getName()+"解锁：identifier"+":"+identifier);
                    List<Object> results = transaction.exec();
                    if (results == null) {
                        continue;
                    }
                    retFlag = true;
                }
                jedis.unwatch();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        	// close(jedis);
        }
        return retFlag;
    }
    
}


